/*
 * protocol_demo.c - 通信协议测试
 * 
 * Author: xiaobo (xiaobocwb@gmail.com)
 * Version: 1.0.0
 * Date: 2024-01
 * 
 * Copyright (c) 2024 xiaobo
 * Licensed under the MIT License
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <poll.h>
#include <semaphore.h>
#include <pthread.h>
#include "../protocol.h"
#include "../port_posix.h"


const char* get_time_str() {
    static char time_str[30];
    struct tm *local_time;
    struct timespec spec;

    clock_gettime(CLOCK_REALTIME, &spec);
    local_time = localtime(&spec.tv_sec);

    strftime(time_str, 30, "%Y-%m-%d %H:%M:%S", local_time);
    sprintf(time_str + 19, ",%03ld", spec.tv_nsec / 1000000);

    return time_str;
}

#define LOG_WITH_TIMESTAMP(log_level, fmt, ...) do { \
    printf("%s - [C]  %s - " fmt "\n", get_time_str(), log_level, ##__VA_ARGS__); \
} while (0)

#define DEBUG_LOG(fmt, ...) LOG_WITH_TIMESTAMP("DEBUG", fmt, ##__VA_ARGS__)
#define INFO_LOG(fmt, ...) LOG_WITH_TIMESTAMP("INFO", fmt, ##__VA_ARGS__)
#define WARN_LOG(fmt, ...) LOG_WITH_TIMESTAMP("WARN", fmt, ##__VA_ARGS__)
#define ERROR_LOG(fmt, ...) LOG_WITH_TIMESTAMP("ERROR", fmt, ##__VA_ARGS__)



#define UNUSED(x) (void)(x)
#define BUFFER_SIZE     1024    // 用于读文件/串口数据时的缓存，以及定义OTA每包数据量，越小越增加API调用频次


// 消息定义（需要通信双方互相约定一致）
/************************************ */
#define TEST_TOPIC      0x01
#define OTA_INFO_TOPIC  0x02
#define OTA_DATA_TOPIC  0x03
#define OTA_END_TOPIC   0x04
/************************************ */

static uint32_t g_test_counter = 0;





/*******************************************************************************
 * 测试代码
 *******************************************************************************/
static void message_callback(uint8_t msgid, uint8_t *payload, size_t len) {
    uint32_t value = *(uint32_t*)payload;
    INFO_LOG("DTU 收到测试消息 msgId=%d, len=%ld, value=%d", msgid, len, value);
}

static void publish_test_message(protocol_t proto) {
    
    INFO_LOG("Turbo 发布数据 %d 到 msgID: %d, 然后等待应答", g_test_counter, TEST_TOPIC);
    if (protocol_publish(proto, TEST_TOPIC, (uint8_t*)&g_test_counter, sizeof(g_test_counter), true, 1000)) {
        INFO_LOG("Turbo 发送成功，等待到应答");
        g_test_counter++;
    } else {
        WARN_LOG("Turbo 发送失败，没等待到应答");
    }
}



bool test_dtu_ota_download(protocol_t proto, const char *file_path) {
    FILE *file = fopen(file_path, "rb");
    if (!file) {
        ERROR_LOG("DTU 打开文件失败");
        return false;
    }

    size_t file_size;
    fseek(file, 0, SEEK_END);
    file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    INFO_LOG("DTU 发送 OTA 信息话题");
    if (!protocol_publish(proto, OTA_INFO_TOPIC, (uint8_t*)&file_size, sizeof(file_size), true, 100)) {
        ERROR_LOG("DTU 发送 OTA 信息话题失败");
        fclose(file);
        return false;
    }else{
        INFO_LOG("DTU 发送 OTA 信息话题成功");
    }

    uint8_t *rx_buffer = malloc(BUFFER_SIZE);
    if (!rx_buffer) {
        ERROR_LOG("Failed to allocate memory for rx_buffer");
        fclose(file);
        return false;
    }

    size_t send_size = 0;
    float byte_time = 115200 / 1000000.0f * 1.2;
    float total_time = file_size * byte_time;
    INFO_LOG("升级文件大小: %ld, 预计发送时间: %.1fms", file_size, total_time);

    while (!feof(file)) {
        size_t read_size = fread(rx_buffer, 1, BUFFER_SIZE, file);
        if (read_size > 0){
            send_size += read_size;
            
            if (!protocol_publish_try(proto, OTA_DATA_TOPIC, rx_buffer, read_size, true, read_size * byte_time, 3)) {
                ERROR_LOG("DTU 尝试 3 次依然发送失败，退出测试");
                free(rx_buffer);
                fclose(file);
                return false;
            }else{
                INFO_LOG("DTU 发送进度 %.2f%%", (float)send_size / file_size * 100);
            }
        }else{
            WARN_LOG("DTU 读取文件异常");
            break;
        }
        usleep(1 * 1000);  // 虚拟串口测试发送太快了，这里模拟正常波特率延时
    }
    INFO_LOG("DTU 发送 OTA 结束话题");
    if (!protocol_publish_try(proto, OTA_END_TOPIC, NULL, 0, true, 50, 3)) {
        ERROR_LOG("DTU 发送 OTA 结束话题失败");
        fclose(file);
        free(rx_buffer);
        return false;
    }else{
        INFO_LOG("DTU 发送 OTA 结束话题成功");
    }   

    fclose(file);
    free(rx_buffer);
    return true;
}

static void ota_info_callback(uint8_t msgid, uint8_t *payload, size_t len) {
    INFO_LOG("Turbo 收到 OTA 信息 msgId=%d, len=%ld, payload=%d", msgid, len, *(uint32_t*)payload);
    INFO_LOG("Turbo 接收到升级信息，启动擦除进程，准备开始下载");
}

static void ota_data_callback(uint8_t msgid, uint8_t *payload, size_t len) {
    UNUSED(payload);
    INFO_LOG("Turbo 收到 OTA 数据 msgId=%d, len=%ld", msgid, len);
    // 下载进程
}

static void ota_end_callback(uint8_t msgid, uint8_t *payload, size_t len) {
    UNUSED(payload);
    INFO_LOG("Turbo 收到 OTA 结束 msgId=%d, len=%ld", msgid, len);
    // 下载完成，通知用户，需要重启设备
}

/********************************************************************************/






// 串口接收解析线程（消息回调也由该线程调用，为了减少对解析影响回调函数要尽快返回）
// rtos平台下 消息回调函数中如果用到大量栈空间，需要注意此线程栈大小
static void* recv_thread_func(void* arg) {
    protocol_t proto = (protocol_t)arg;
    uint8_t rx_buffer[BUFFER_SIZE];

    void *dev = protocol_get_dev(proto);
    while (1) {
        ssize_t bytes_read = uart_recv(dev, rx_buffer, sizeof(rx_buffer), 100);
        if (bytes_read > 0) {
            protocol_prase(proto, rx_buffer, bytes_read);
        }
    }

    return NULL;
}

int main(int argc, char *argv[]) {

    // 测试程序，通过传入的参数决定是模拟作为 DTU 还是 turbo 主板
    /********************************************** */
    char *serial_port;
    if (argc > 1) {
        serial_port = argv[1];
    }
    
    bool is_dtu = false;

    if (strcmp(serial_port, "/tmp/ttyVSP1") == 0){
        is_dtu = true;
        INFO_LOG("ttyVSP1 作为 DTU 设备");
    }else{
        INFO_LOG("ttyVSP2 作为 turbo 主板");
    }

    /********************************************** */


    // 1. 初始化hal 创建协议实例
    /********************************************** */
    int fd = open_serial_port(serial_port);
    if (fd < 0) {
        ERROR_LOG("Failed to open serial port %s", serial_port);
        return 1;
    }

    INFO_LOG("Serial port %s opened successfully", serial_port);

    protocol_hal_t hal = hal_posix; // 拷贝posix 默认 hal
    hal.dev = (void*)&fd;
    

    protocol_t proto = protocol_create(&hal);
    if (!proto) {
        ERROR_LOG("Failed to create protocol instance");
        close(fd);
        return 1;
    }

    // 创建串口接收解析线程
    pthread_t thread;
    pthread_create(&thread, NULL, recv_thread_func, proto);

    /********************************************** */

    printf("\n");

    // 2. 订阅主题
    /********************************************** */
    if (is_dtu){    
        // 订阅测试主题
        if (!protocol_subscribe(proto, TEST_TOPIC, message_callback)) {
            ERROR_LOG("DTU 订阅测试话题失败 msgId: %d", TEST_TOPIC);
            protocol_destroy(proto);
            close(fd);
            return 1;
        }else{
            INFO_LOG("DTU 订阅测试话题成功 msgId: %d", TEST_TOPIC);
        }
    }else{
        // 订阅 OTA 主题
        if (!protocol_subscribe(proto, OTA_INFO_TOPIC, ota_info_callback)) {
            ERROR_LOG("Turbo 订阅 OTA 信息话题失败 msgId: %d", OTA_INFO_TOPIC);
            protocol_destroy(proto);
            close(fd);
            return 1;
        }else{
            INFO_LOG("Turbo 订阅 OTA 信息成功 msgId: %d", OTA_INFO_TOPIC);
        }
        if (!protocol_subscribe(proto, OTA_DATA_TOPIC, ota_data_callback)) {
            ERROR_LOG("Turbo 订阅 OTA 数据话题失败 msgId: %d", OTA_DATA_TOPIC);
            protocol_destroy(proto);
            close(fd);
            return 1;
        }else{
            INFO_LOG("Turbo 订阅 OTA 数据成功 msgId: %d", OTA_DATA_TOPIC);
        }
        if (!protocol_subscribe(proto, OTA_END_TOPIC, ota_end_callback)) {
            ERROR_LOG("Turbo 订阅 OTA 结束话题失败 msgId: %d", OTA_END_TOPIC);
            protocol_destroy(proto);
            close(fd);
            return 1;
        }else{
            INFO_LOG("Turbo 订阅 OTA 结束成功 msgId: %d", OTA_END_TOPIC);
        }
    }
    /********************************************** */

    sleep(1);   // 等待另一个测试进程启动
    printf("\n");

    // 3. 测试代码
    /********************************************** */
    if (is_dtu) {
        if(!test_dtu_ota_download(proto, "../firmware.txt")){
            ERROR_LOG("DTU OTA 测试失败");
        }else{
            INFO_LOG("DTU OTA 测试成功");
        }
    }else{
        INFO_LOG("Turbo 主板 0.01 秒发送一次测试消息");
        uint32_t counter = 30;
        while (counter--) {
            publish_test_message(proto);
            // sleep(1);
            usleep(10 * 1000);
        }
    }
    /********************************************** */

    pthread_join(thread, NULL);
    protocol_destroy(proto);
    close(fd);
    return 0;
} 